// Gets rid of Carbon compile errors ...
#include "../../Epsilon/window.h"

#include "room.h"
#include "sector.h"

#include "states.h"
#include "../console.h"

#include "../../Epsilon/STL.h"
#include "../../Epsilon/Epsilon3D/rendercontext.h"

using namespace Underlight;

Room::Room (void) : id (-1)
{
  vcache = cli_parse::states->render_context->CreateVertexCache ();
  loaded = false;
}

Room::Room (int id_) : id (id_)
{
  vcache = cli_parse::states->render_context->CreateVertexCache ();
  loaded = false;
}

Room::~Room (void)
{
  if (vcache != NULL) {
    delete vcache;
    vcache = NULL;
  }

  for (unsigned int i = 0; i < sectors.size (); i++)
    delete [] sectors [i];

  sectors.clear ();
}

void
Room::AddSector (Sector* sector)
{
  if (! sector)
    return;

  if (! ContainsSector (sector->id))
    sectors.push_back (sector);
}

bool
Room::ContainsSector (int id) const
{
  return (GetSector (id) != NULL);
}

Sector*
Room::GetSector (int id) const
{
  std::vector<Sector*>::const_iterator sector = sectors.begin ();
  std::vector<Sector*>::const_iterator end    = sectors.end   ();
  
  while (eSTL::IterValid (sector, end)) {
    if ((*sector)->id == id) {
      return (*sector);
    }

    ++sector;
  }

  return NULL;
}


#include "../../Epsilon/Epsilon3D/immediate.h"

#include "level.h"
#include "tesselation.h"
#include "debug.h"

int clamp_light (int light)
{
  light = (int) ((float)light * 1.25f);

  if (light < 1)
    light = 1;

  if (light > 50)
    light = 50;

  return light;
}

void
PurgeUnusedSurfaces (Sector* sec)
{
  if (sec->floor && (! sec->floor->Baseline ())) {
    delete sec->floor;
    sec->floor = NULL;

    //eTB_Printf ("Purged unused floor surface [Sector: %d]\n", sec->id);
  }

  if (sec->ceil && (! sec->ceil->Baseline ())) {
    delete sec->ceil;
    sec->ceil = NULL;

    //eTB_Printf ("Purged unused ceil surface [Sector: %d]\n", sec->id);
  }
}

#include "render_batch.h"

void
Room::Unload (void)
{
  if (! loaded)
    return;

  cli_parse::debug->MemoryCheckpoint (_T ("Begin Room::Unload (...)"));

  batches.clear ();

  std::vector<Sector*>::iterator sector = sectors.begin ();
  std::vector<Sector*>::iterator end    = sectors.end   ();

  while (eSTL::IterValid (sector, end)) {
    (*sector)->Unload ();

    ++sector;
  }

  if (vcache) {
    delete vcache;
    vcache = NULL;
  }

  // We create new combined batches as an optimization,
  //  they will not be freed with a sector unload.
  //
  //  * So we free all of the combined batches here.

  std::vector<cli_parse::RenderBatch*>::iterator batch      = batches.begin ();
  std::vector<cli_parse::RenderBatch*>::iterator last_batch = batches.end   ();

  while (eSTL::IterValid (batch, last_batch)) {
    if ((*batch)->generated) {
      delete (*batch);
    }

    ++batch;
  }

  /*
  batch      = animated_batches.begin ();
  last_batch = animated_batches.end   ();

  while (eSTL::IterValid (batch, last_batch)) {
    if ((*batch)->generated) {
      delete (*batch);
    }

    ++batch;
  }
  */

  //sectors.clear ();

  loaded = false;

  cli_parse::debug->MemoryCheckpoint (_T ("End Room::Unload (...)"));
}

#include "textures.h" // Underlight::TextureManager
#include "effect.h"   // Underlight::EffectManager

#include "bsp.h" // eTB_Polygon

bool
Room::Load (Level* lvl)
{
  if (loaded)
    return true;

  cli_parse::debug->MemoryCheckpoint (_T ("Begin Room::Load (...)"));

  eTB_Printf ("Room: %#2d (%#32s) <Size: %3.1f Kb>\n", id,
          name, MemSize () / 1024.0f);

  if (! vcache)
    vcache = cli_parse::states->render_context->CreateVertexCache ();

  std::vector<Sector*>::iterator sector = sectors.begin ();
  std::vector<Sector*>::iterator end    = sectors.end   ();

  while (eSTL::IterValid (sector, end)) {
    PurgeUnusedSurfaces ((*sector));

    // Skip NoDraw sectors to speed things up...
    if ((*sector)->IsNoDraw ()) {
      ++sector;
      continue;
    }

    tess::Clear ();

    // Calculate the wall surfaces (floor walls and ceiling walls)
    (*sector)->CalculateWalls ();

    // Calculate the flat surfaces (floors and ceilings)
    (*sector)->CalculateSurfaces (true);

    int x = 0;

    std::vector< tess::Polygon >::const_iterator poly      = tess::GetFirst ();
    std::vector< tess::Polygon >::const_iterator last_poly = tess::GetLast  ();

    /*
    if (poly == last_poly) {
      eTB_Printf ("No floor/ceiling surface for sector %d\n",
                     (*sector)->id);
      sector++;
      continue;
    }
    */

    while (eSTL::IterValid (poly, last_poly)) {
      Sector::Flat* sector_flat = new Sector::Flat ((*poly).type ());

      e3dBeginIndexed (vcache, sector_flat->floor);

        eTB_Polygon planar_poly;
        int         num_planar_verts = 0;

        std::vector< tess::Vertex >::const_iterator vtx = (*poly).begin ();
        std::vector< tess::Vertex >::const_iterator end = (*poly).end   ();

        // Use the first 3 vertices to calculate the floor's normal
        while (eSTL::IterValid (vtx, end) && num_planar_verts <= 2) {
          ++num_planar_verts;

          planar_poly.addVertex (eTB_Vertex (e3dVector3 (vtx->x, vtx->y, (*sector)->FloorHt (vtx->x, vtx->y))));
          ++vtx;
        }

        planar_poly.calculatePlane ();

        e3dVector3 normal = planar_poly.getPlane ().normal;

        vtx = (*poly).begin ();
        end = (*poly).end   ();

        float u = 0.0f;
        float v = 0.0f;

        float light_level = (1.0f - (clamp_light ((*sector)->light_level) / 50.0f)) / cli_parse::states->r_light_scale;

        /* Quadratic Attenuation... */
//        light_level = (light_level * light_level);

        e3dColor3f (light_level, light_level, light_level);

        while (eSTL::IterValid (vtx, end)) {
        /*
          switch (x % 3) {
            case 0:
              e3dColor3f (0.0f, 0.0f, 1.0f);
              break;
            case 1:
              e3dColor3f (0.0f, 1.0f, 1.0f);
              break;
            case 2:
              e3dColor3f (1.0f, 1.0f, 1.0f);
              break;
          }
          */

          (*sector)->TexCoordFloor (vtx->x, vtx->y, u, v);

          e3dTexCoord2f (u, v);
          e3dNormal3f   (normal.x, normal.y, normal.z);
          e3dVertex3f   (vtx->x, vtx->y, (*sector)->FloorHt (vtx->x, vtx->y));

          ++vtx;
          x++;
        }

      e3dEnd ();

      if (! ((*sector)->flags & SECTOR_SKY)) {
        e3dBeginIndexed (vcache, sector_flat->ceil);

          int x = 0;

          std::vector< tess::Vertex >::const_iterator vtx   = (*poly).end   ();
          std::vector< tess::Vertex >::const_iterator begin = (*poly).begin ();

          eTB_Polygon planar_poly;
          int         num_planar_verts = 0;

          while (eSTL::IterValid (vtx, begin) && num_planar_verts <= 2) {
            --vtx;
            ++num_planar_verts;

            planar_poly.addVertex (eTB_Vertex (e3dVector3 (vtx->x, vtx->y, (*sector)->CeilHt (vtx->x, vtx->y))));
          }

          planar_poly.calculatePlane ();

          e3dVector3 normal = planar_poly.getPlane ().normal;

          vtx   = (*poly).end   ();
          begin = (*poly).begin ();

          e3dColor3f (light_level, light_level, light_level);

          while (eSTL::IterValid (vtx, begin)) {
            --vtx;

            /*
            switch (x % 3) {
              case 0:
                e3dColor3f (1.0f, 0.0f, 0.0f);
                break;
              case 1:
                e3dColor3f (1.0f, 1.0f, 0.0f);
                break;
              case 2:
                e3dColor3f (1.0f, 1.0f, 1.0f);
                break;
            }
            */

            (*sector)->TexCoordCeil (vtx->x, vtx->y, u, v);

            e3dTexCoord2f (u, v);
            e3dNormal3f   (normal.x, normal.y, normal.z);
            e3dVertex3f   (vtx->x, vtx->y, (*sector)->CeilHt (vtx->x, vtx->y));

            x++;
          }

        e3dEnd ();
      }

      (*sector)->flats.push_back (sector_flat);

      ++poly;
    }

    ++sector;
  }

  sector = sectors.begin ();

  while (eSTL::IterValid (sector, end)) {
    // Added 6/19/2011 - skip NoDraw sectors to speed things up...
    if ((*sector)->IsNoDraw ()) {
      //eTB_Printf ("Skipping Batch Generation for NO DRAW sector: %d\n", (*sector)->id);
      ++sector;
      continue;
    }

    (*sector)->CacheBatches ();
    (*sector)->CachePickingBatches ();

    std::sort ((*sector)->batches.begin (), (*sector)->batches.end (), cli_parse::RenderBatch::SortByTexture ());


    ++sector;
  }

  AddBatches  (NULL);
  SortBatches ();

  Underlight::effects->LoadSkyBitmap (background);
  BITMAPINFO_4DX* bitmap = Underlight::effects->GetBitmap (background);

  /*
  Save4DXToFile ( bitmap,
                  stringf (_T ("skies/%d.tga"), background),
                  Underlight::VisualEffect );
  */

  tess::Clear ();

  loaded = true;

  eTB_Printf (_T ("Room: %#2d (%#32s) <Size: %3.1f Kb>\n"), id,
                name, MemSize () / 1024.0f);

  eTB_Printf (_T ("EffectManager:  ( Visual Effects.: %7.1f Kb\n")
              _T ("                  Sounds.........: %7.1f Kb )\n"),
                Underlight::effects->VEBytes () / 1024.0f,
                Underlight::effects->SEBytes () / 1024.0f);

  eTB_Printf (_T ("TextureManager: ( Textures.......: %7.1f Kb\n")
              _T ("                  Flats..........: %7.1f Kb )\n"),
                Underlight::textures->TexBytes ()  / 1024.0f,
                Underlight::textures->FlatBytes () / 1024.0f);

  cli_parse::debug->MemoryCheckpoint (_T ("End Room::Load (...)"));

  return true;
}


size_t
Room::MemSize (void)
{
  size_t size = sizeof (Room);

  if (sectors.size ()) {
    for (unsigned int i = 0; i < sectors.size (); i++) {
      size += sectors [i]->size ();
    }
  }

  if (vcache != NULL) {
    size += vcache->MemSize ();
  }

  return size;
}



void
Room::AddBatches (Sector* sector_)
{
  batches.clear          ();
  animated_batches.clear ();

  std::vector<Sector*>::const_iterator sector   = sectors.begin ();
  std::vector<Sector*>::const_iterator last_sec = sectors.end   ();

  while (eSTL::IterValid (sector, last_sec)) {
    std::vector<cli_parse::RenderBatch*>::const_iterator batch      =
        (*sector)->batches.begin ();
    std::vector<cli_parse::RenderBatch*>::const_iterator last_batch =
        (*sector)->batches.end   ();

    while (eSTL::IterValid (batch, last_batch)) {
      batches.push_back (*batch);

      ++batch;
    }

    ++sector;
  }
}

bool
IsCombinable (cli_parse::RenderBatch* combined, cli_parse::RenderBatch* candidate)
{
  // Cannot combine triangle fans or strips
  if ((combined->primitive_type != candidate->primitive_type))
    return false;

  if ((combined->animated   != candidate->animated)   ||
      (combined->tex_id     != candidate->tex_id)     ||
      (combined->tex_frames != candidate->tex_frames) ||
      (combined->xanim      != candidate->xanim)      ||
      (combined->yanim      != candidate->yanim)) {
    return false;
  }

  return true;
}

cli_parse::RenderBatch*
FindBatch ( std::vector<cli_parse::RenderBatch*>& batches,
            cli_parse::RenderBatch*               test )
{
  std::vector<cli_parse::RenderBatch*>::iterator batch = batches.begin ();
  std::vector<cli_parse::RenderBatch*>::iterator end   = batches.end   ();

  while (eSTL::IterValid (batch, end)) {
    if (IsCombinable ((*batch), test))
      return (*batch);

    ++batch;
  }

  return NULL;
}


void
Room::SortBatches (void)
{
  std::vector<cli_parse::RenderBatch*> temp     (batches.begin (), batches.end ());
  std::vector<cli_parse::RenderBatch*> anim_tmp;

  batches.clear ();

  std::vector<cli_parse::RenderBatch*>::iterator batch      = temp.begin ();
  std::vector<cli_parse::RenderBatch*>::iterator last_batch = temp.end   ();

  int i = 0;

  //std::sort (batches.begin (), batches.end (), cli_parse::RenderBatch::SortByTexture ());
  //
  // XXX: This is a REALLY stupid way of doing this, but std::sort seems to
  //       fsck up the heap for some reason.
  //
  for (i = 0; i < MAX_TEXTURES; i++) {
    batch = temp.begin ();
    
    while (eSTL::IterValid (batch, last_batch)) {
      if ((*batch)->tex_id == i) {
        if ((*batch)->animated == 0) {
          batches.push_back (*batch);
        } else {
          anim_tmp.push_back (*batch);
        }
      }

      ++batch;
    }
  }

  // Sort the animated batches by their operation
  for (i = 1; i < 3; i++) {
    batch      = anim_tmp.begin ();
    last_batch = anim_tmp.end ();

    while (eSTL::IterValid (batch, last_batch)) {
      if ((*batch)->animated == i) {
        animated_batches.push_back ((*batch));
      }

      ++batch;
    }
  }

  //std::sort (animated_batches.begin (), animated_batches.end (), cli_parse::RenderBatch::SortByAnimationType ());


  std::vector<cli_parse::RenderBatch*> combined;

  batch      = batches.begin ();
  last_batch = batches.end   ();

  while (eSTL::IterValid (batch, last_batch)) {
    cli_parse::RenderBatch* pBatch = FindBatch (combined, (*batch));

    if (! pBatch) {
      combined.push_back (*batch);
    } else {
      if (! IsCombinable (pBatch, (*batch))) {
        combined.push_back (*batch);
      } else {
        cli_parse::RenderBatch* pBatchOld = pBatch;

        pBatch = pBatch->Combine (*(*batch));

        if (pBatch != pBatchOld) {
          combined.pop_back ();

          pBatch->tex_id     = pBatchOld->tex_id;
          pBatch->tex_frames = pBatchOld->tex_frames;
          pBatch->type       = pBatchOld->type;

          pBatch->xanim      = pBatchOld->xanim;
          pBatch->yanim      = pBatchOld->yanim;
          pBatch->animated   = pBatchOld->animated;

          //combined_batch->surface = this->floor;
          //combined_batch->sector  = this;
          pBatch->primitive_type = pBatchOld->primitive_type;

          combined.push_back (pBatch);
        }
      }
    }

    ++batch;
  }

  batches.clear ();
  batches = combined;


  combined.clear ();


  batch      = animated_batches.begin ();
  last_batch = animated_batches.end   ();

  while (eSTL::IterValid (batch, last_batch)) {
    cli_parse::RenderBatch* pBatch = FindBatch (combined, (*batch));

    if (! pBatch) {
      combined.push_back (*batch);
    } else {
      if (! IsCombinable (pBatch, (*batch))) {
        combined.push_back (*batch);
      } else {
        cli_parse::RenderBatch* pBatchOld = pBatch;

        pBatch = pBatch->Combine (*(*batch));

        if (pBatch != pBatchOld) {
          combined.pop_back ();

          pBatch->tex_id     = pBatchOld->tex_id;
          pBatch->tex_frames = pBatchOld->tex_frames;
          pBatch->type       = pBatchOld->type;

          pBatch->xanim      = pBatchOld->xanim;
          pBatch->yanim      = pBatchOld->yanim;
          pBatch->animated   = pBatchOld->animated;

          //combined_batch->surface = this->floor;
          //combined_batch->sector  = this;
          pBatch->primitive_type = pBatchOld->primitive_type;

          combined.push_back (pBatch);
        }
      }
    }

    ++batch;
  }

  animated_batches.clear ();
  animated_batches = combined;
}
